import Game from "../../assets/script/common/game/Game";
import GameServer from "./GameServer";
import { Protocol, ProtoVo } from "../../assets/script/common/protocol/Protocol";
import PlayerSnake from "../../assets/script/common/game/snake/PlayerSnake";
import Snake from "../../assets/script/common/game/snake/Snake";
import ProtoDispatcher from "../../assets/script/common/core/ProtoDispatcher";
import Config from "../../assets/script/common/game/config/Config";

type WebSocket = import("ws/index")

export default class PlayerConn {

    /** 玩家对应蛇实例 */
    private _snake: PlayerSnake

    /** 玩家id */
    get id() { return this._snake.id }
    /** 协议派发器 */
    private _pd: ProtoDispatcher = new ProtoDispatcher()
    /** 协议派发器 */
    get pd() { return this._pd }

    /** 世界实例 */
    get world() { return this.game.world }


    private _isReady = false
    get isReady() { return this._isReady }

    /** 加载中的蛇映射 */
    private _loadedSnakeMap = new Map<number, Snake>()
    /** 加载中的蛇集合 */
    private _loadedSnakeSet = new Set<number>()
    /** 可见蛇节点集合 */
    private _screenNodeSet = new Set<number>()
    /** 可见蛇节点集合 */
    private _screenFoodSet = new Set<number>()

    constructor(
        /** 服务器 */
        readonly server: GameServer,
        /** 游戏实例 */
        readonly game: Game,
        /** scoket连接 */
        readonly conn: WebSocket) {

        this._pd.on(Protocol.C2S_JOIN, this, data => {

            let { name } = data

            //防止无名
            if (!name) {
                this.send(Protocol.S2C_JOIN_ERR, { msg: "请输入名称" })
                conn.close()
                return
            }
            //防止模仿电脑人
            if (name.includes(Config.AISnake.namePre)) {
                this.send(Protocol.S2C_JOIN_ERR, { msg: "你是电脑人？" })
                conn.close()
                return
            }

            //加入成功
            this._snake = game.join(name)
            this.server.join(this)
            this._pd.on(Protocol.C2S_READY, this, data => this.onReady())
            this.send(Protocol.S2C_JOIN_OK, { id: this._snake.id })
            this.onJoin()
        })

        conn.on("close", e => {
            this.onDisconnect()
        })

        conn.on("message", (data: any) => {
            this._pd.onMsg(data)
        })
    }

    /** 发送协议 */
    send<Protocol extends keyof ProtoVo.Def>(protocol: Protocol, data?: ProtoVo.Def[Protocol]) {
        this.conn.send(this._pd.from(protocol, data))
    }

    /** 加入游戏后 */
    private onJoin() {
        console.log('<' + this._snake.name + '> 加入了游戏')
        //接收操作
        this._pd.on(Protocol.C2S_CTRL_TURN, this, (data: any) => this.onCtrlTurn(data))
        this._pd.on(Protocol.C2S_CTRL_SPRINT, this, (data: any) => this.onCtrlSprint(data))
    }

    /** 断开连接 */
    private onDisconnect() {
        if (this._snake) {
            console.log('<' + this._snake.name + '> 离开了游戏')
            this.server.exit(this.id)
        }
    }

    private onReady() {
        this._isReady = true
        //发送世界大小
        this.sendWorldSize()
        this.sendSnake()
    }


    private onCtrlTurn(data: ProtoVo.IC2S_CTRL_TURN) {
        this._snake.ctrlTurn(data.ang / 100)

    }

    private onCtrlSprint(data: ProtoVo.IC2S_CTRL_SPRINT) {
        this._snake.ctrlSprint(data.isSprint)
    }


    /** 发送世界大小 */
    sendWorldSize() {
        this.send(Protocol.S2C_WORLD_SIZE, this.world.size)
    }

    /** 发送蛇 */
    sendSnake() {

        let { loadSnakes: _loadSnakes, unloadSnakeIdSet } = this._snake.updateLoadedSnakeMap(this._loadedSnakeSet)

        let loadSnakes: ProtoVo.IS2C_SNAKE_LOAD_SNAKE[] = []
        let unloadSnakeIds: number[] = []
        _loadSnakes.forEach(snake => {
            let _snake: ProtoVo.IS2C_SNAKE_LOAD_SNAKE = {
                id: snake.id,
                name: snake.name
            }
            if (snake.head) {
                _snake.hid = snake.head.id
            }
            loadSnakes.push(_snake)
            this._loadedSnakeMap.set(snake.id, snake)
        })
        unloadSnakeIdSet.forEach(snakeId => {
            unloadSnakeIds.push(snakeId)
            this._loadedSnakeMap.delete(snakeId)
        })
        loadSnakes.length && this.send(Protocol.S2C_SNAKE_LOAD, { loadSnakes })
        unloadSnakeIds.length && this.send(Protocol.S2C_SNAKE_UNLOAD, { unloadSnakeIds: Array.from(unloadSnakeIdSet) })
    }

    /** 发送蛇节点 */
    sendSnakeNodes() {
        let { loadNodeMap, unloadNodeIdSet } = this._snake.updateScreenNodeMap(this._screenNodeSet)

        let loadSnakeMap = new Map<number, ProtoVo.IS2C_SNAKE_NODE_LOAD_SNAKE>()
        loadNodeMap.forEach((nodes, snake) => {
            let loadNodes: ProtoVo.IS2C_SNAKE_NODE_LOAD_NODE[] = []
            loadSnakeMap.set(snake.id, {
                id: snake.id,
                w: Math.floor(snake.getWidth() * 100),
                rgbs: snake.rgbs,
                loadNodes
            })
            nodes.forEach(node => {
                loadNodes.push({
                    id: node.id,
                    x: Math.floor(node.x * 100),
                    y: Math.floor(node.y * 100),
                    ang: Math.floor(node.ang * 100),
                    idx: node.idx
                })
            })
        })

        let loadSnakes = Array.from(loadSnakeMap.values())
        let unloadNodeIds = Array.from(unloadNodeIdSet)
        loadSnakes.length && this.send(Protocol.S2C_SNAKE_NODE_LOAD, { loadSnakes })
        unloadNodeIds.length && this.send(Protocol.S2C_SNAKE_NODE_UNLOAD, { unloadNodeIds })

    }

    /** 发送食物 */
    sendFoods() {
        let { loadFoods: _loadFoods, unloadFoodMap } = this._snake.updateScreenFoodMap(this._screenFoodSet)

        let loadFoods: ProtoVo.IS2C_FOOD_LOAD_FOOD[] = []
        let unloadFoods: ProtoVo.IS2C_FOOD_UNLOAD_FOOD[] = []

        _loadFoods.forEach(food => {
            loadFoods.push({
                id: food.id,
                x: Math.floor(food.x * 100),
                y: Math.floor(food.y * 100),
                w: Math.floor(food.w * 100),
                rgb: food.color ? food.rgb + 1 : undefined
            })
        })

        unloadFoodMap.forEach((eaterId, foodId) => {
            unloadFoods.push({
                id: foodId,
                eaterId
            })
        })


        loadFoods.length && this.send(Protocol.S2C_FOOD_LOAD, { loadFoods })
        unloadFoods.length && this.send(Protocol.S2C_FOOD_UNLOAD, { unloadFoods })
    }

    sendHid(data: ProtoVo.IS2C_SNAKE_HID_UPDATE) {
        data.snakes.length && this.send(Protocol.S2C_SNAKE_HID_UPDATE, data)
    }

}